// Copyright 2006 Two Pair, LLC. All Rights Reserved.

/**
 * A phrase of words. Note: this class has a natural ordering that is
 * inconsistent with equals. Equality and hashcode are based only on the
 * contained words, so that phrases can quickly be looked up in a hash map.
 * Comparison order, on the other hand, is as follows. Phrases with higher
 * frequency come first in comparison order. Among phrases with equal
 * frequencies, comparison order is alphabetic, from left to right.
 */
public class Phrase implements Comparable {

  /**
   * An initial hash value, based on recommendations by Josh Bloch in
   * <i>Effective Java</i>
   */
  private static final int HASH_SEED = 23;

  /**
   * A hash term multiplier, based on recommendations by Josh Bloch in
   * <i>Effective Java</i>
   */
  private static final int ODD_PRIME = 37;

  /**
   * Construct a phrase with the given sequence of words.
   * 
   * @param words a sequence of words that will comprise the new phrase
   */
  public Phrase(String[] words) {
    this.words = words;
  }

  /*
   * @see java.lang.Comparable#compareTo(java.lang.Object)
   */
  public int compareTo(Object o) {
    Phrase other = (Phrase) o;

    /*
     * Confusing: The higher-frequency phrase is ordered first; which means it
     * must be logically considered less than the lower-frequency phrase.
     * 
     * The contract of compareTo is to return a negative number if this phrase
     * is less than the other phrase. So we need to return a negative number if
     * this object's frequency is greater than the other object's frequency.
     */
    if (frequency > other.frequency) {
      return -1;
    } else if (frequency < other.frequency) {
      return 1;
    }

    // Now we just sort alphabetically
    // ASSERT: words.length == other.words.length
    for (int i = 0; i < words.length; ++i) {
      int compareTo = words[i].compareTo(other.words[i]);
      if (compareTo != 0) {
        return compareTo;
      }
    }

    // they are equal
    return 0;
  }

  /*
   * @see java.lang.Object#equals(java.lang.Object)
   */
  public boolean equals(Object obj) {
    if (!(obj instanceof Phrase)) {
      return false;
    }

    // equality is based on the equality of the component words
    Phrase other = (Phrase) obj;
    // ASSERT: words.length == other.words.length
    for (int i = 0; i < words.length; ++i) {
      if (!words[i].equals(other.words[i])) {
        return false;
      }
    }

    return true;
  }

  /**
   * @return the frequency of this phrase
   */
  public long getFrequency() {
    return frequency;
  }

  /**
   * @return the words of this phrase
   */
  public String[] getWords() {
    return words;
  }

  /*
   * @see java.lang.Object#hashCode()
   */
  public int hashCode() {
    if (hash == 0) {
      // hashcode is based on the hashcode of the component words
      int h = HASH_SEED;
      int len = words.length;
      for (int i = 0; i < len; ++i) {
        h *= ODD_PRIME;
        h += words[i].hashCode();
      }
      hash = h;
    }
    return hash;
  }

  /**
   * Increment the phrase's frequency.
   */
  public void incrementFrequency() {
    ++frequency;
  }

  /**
   * The frequency of this phrase.
   */
  private long frequency = 1;

  /**
   * Cache of the hash code for this phrase.
   */
  private int hash = 0;

  /**
   * The sequence of words that comprises this phrase.
   */
  private final String[] words;

}
